package edu.towson.cis.cosc603.project4.vendingmachine;

import static org.junit.Assert.*;
import org.junit.rules.ExpectedException;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

/**
 * The unit test Class for VendingMachineItem.
 */
public class VendingMachineTest {

    /** Declaring necessary test objects for {@link VendingMachineItem} */
    VendingMachine machine1;
    VendingMachineItem item1, item2;

    /** Declare expected exception */
    @Rule
    public ExpectedException exception = ExpectedException.none();

    /**
     * Initializes the necessary test objects for the test cases to use.
     *
     * @throws Exception the exception
     */
    @Before
    public void setUp() throws Exception {

        machine1 = new VendingMachine();
        item1 = new VendingMachineItem("Coca-Cola", 0.42);
        item2 = new VendingMachineItem("Snickers", 0.50);

    }

    /**
     * Test for the addItem() method of the {@link VendingMachine} class.
     */
    @Test
    public void testAddItem() {

        machine1.addItem(item1, VendingMachine.A_CODE);

        assertEquals(item1, machine1.getItem(VendingMachine.A_CODE));

    }

    /**
     * Test for the VendingMachineItem() constructor of the {@link VendingMachineItem} class.
     *
     * Should throw VendingMachineException when slot is already occupied
     */
    @Test
    public void testAddItemThrowAlreadyOccupiedException() {

        exception.expect(VendingMachineException.class);
        exception.expectMessage(VendingMachine.SLOT_MESSAGE + VendingMachine.A_CODE + VendingMachine.ALREADY_OCCUPIED_MESSAGE);

        machine1.addItem(item1, VendingMachine.A_CODE);
        machine1.addItem(item2, VendingMachine.A_CODE);

    }

    /**
     * Test for the VendingMachineItem() constructor of the {@link VendingMachineItem} class.
     *
     * Should throw VendingMachineException when adding item to invalid slot code
     */
    @Test
    public void testAddItemThrowInvalidCodeException() {

        exception.expect(VendingMachineException.class);
        exception.expectMessage(VendingMachine.INVALID_CODE_MESSAGE);

        machine1.addItem(item1, VendingMachine.A_CODE);
        machine1.addItem(item2, "Z");

    }

    /**
     * Test for the removeItem() method of the {@link VendingMachine} class.
     */
    @Test
    public void testRemoveItem() {

        machine1.addItem(item1, VendingMachine.A_CODE);
        machine1.removeItem(VendingMachine.A_CODE);

        assertEquals(null, machine1.getItem(VendingMachine.A_CODE));

    }

    /**
     * Test for the removeItem() method of the {@link VendingMachine} class.
     *
     * Should throw VendingMachineException when slot is already empty
     */
    @Test
    public void testRemoveItemThrowSlotEmptyException() {

        exception.expect(VendingMachineException.class);
        exception.expectMessage(VendingMachine.SLOT_MESSAGE + VendingMachine.A_CODE + VendingMachine.IS_EMPTY_MESSAGE);

        machine1.removeItem(VendingMachine.A_CODE);

    }

    /**
     * Test for the VendingMachineItem() constructor of the {@link VendingMachineItem} class.
     *
     * Should throw VendingMachineException when the code is invalid
     */
    @Test
    public void testRemoveItemThrowInvalidCodeException() {

        exception.expect(VendingMachineException.class);
        exception.expectMessage(VendingMachine.INVALID_CODE_MESSAGE);

        machine1.removeItem("Z");

    }

    /**
     * Test for the insertMoney() method of the {@link VendingMachine} class.
     */
    @Test
    public void testInsertMoney() {

        machine1.insertMoney(1.00);

        assertEquals(1.00, machine1.getBalance(), 0.001);

    }

    /**
     * Test for the insertMoney() method of the {@link VendingMachine} class.
     *
     * Should throw VendingMachineException when insert negative amount of money
     */
    @Test
    public void testInsertMoneyThrowInvalidAmountException() {

        exception.expect(VendingMachineException.class);
        exception.expectMessage(VendingMachine.INVALID_AMOUNT_MESSAGE);

        machine1.insertMoney(-1.00);

    }

    /**
     * Test for the getBalance() method of the {@link VendingMachine} class.
     */
    @Test
    public void testGetBalance() {

        assertEquals(0.00, machine1.getBalance(), 0.001);

        machine1.insertMoney(1.00);

        assertEquals(1.00, machine1.getBalance(), 0.001);
        assertEquals(1.00, machine1.getBalance(), 0.001);

    }

    /**
     * Test for the makePurchase() method of the {@link VendingMachine} class.
     */
    @Test
    public void testMakePurchase() {

        machine1.addItem(item1, VendingMachine.A_CODE);
        machine1.insertMoney(1.00);

        assertEquals(true, machine1.makePurchase(VendingMachine.A_CODE));

    }

    /**
     * Test for the makePurchase() constructor of the {@link VendingMachine} class.
     *
     * Should throw VendingMachineException when the code is invalid
     */
    @Test
    public void testMakePurchaseThrowInvalidCodeException() {

        exception.expect(VendingMachineException.class);
        exception.expectMessage(VendingMachine.INVALID_CODE_MESSAGE);

        machine1.addItem(item1, VendingMachine.A_CODE);
        machine1.insertMoney(1.00);
        machine1.makePurchase("Z");

    }

    /**
     * Test for the makePurchase() constructor of the {@link VendingMachine} class.
     *
     * Try to make purchase with insufficient funds
     */
    @Test
    public void testMakePurchaseInsufficientFunds() {

        machine1.addItem(item1, VendingMachine.A_CODE);
        machine1.insertMoney(0.25);

        assertEquals(false, machine1.makePurchase(VendingMachine.A_CODE));

    }

    /**
     * Test for the makePurchase() constructor of the {@link VendingMachine} class.
     *
     * Try to make purchase from empty slot
     */
    @Test
    public void testMakePurchaseEmptySlot() {

        machine1.insertMoney(0.25);

        assertEquals(false, machine1.makePurchase(VendingMachine.A_CODE));

    }

    /**
     * Test for the returnChange() method of the {@link VendingMachine} class.
     */
    @Test
    public void testReturnChange() {

        machine1.addItem(item1, VendingMachine.A_CODE);
        machine1.insertMoney(1.03);

        machine1.makePurchase(VendingMachine.A_CODE);

        assertEquals(0.61, machine1.returnChange(), 0.001);

    }

    /**
     * Cleans up test objects after a test case is executed.
     */
    @After
    public void tearDown() {
        machine1 = null;
        item1 = null;
    }
}
